package cz.stv.pecinovsky.město;
import cz.stv.pecinovsky.správceplátna.IOvládaný;
import cz.stv.pecinovsky.správceplátna.Řadič;
import cz.stv.pecinovsky.správceplátna.SprávcePlátna;

import cz.stv.pecinovsky.utility.Barva;
import cz.stv.pecinovsky.utility.IO;
import cz.stv.pecinovsky.utility.Pozice;

import cz.stv.pecinovsky.utility.Směr8;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;


/*******************************************************************************
 * Instance třídy {@code Závod_K} představují závody, které je možno absolvovat.
 * Všechny závody se ale jezdí na okruhu vytvářeném metodou
 * {@link Okruh#elko(Pozice, Barva)}.
 * K závodu se může přihlásit několik závodníků, kteří jej pak pojedou
 * všichni současně každý na své instanci okruhu.
 *
 * @author  Rudolf PECINOVSKÝ
 * @version 1.10.2678 — 2011-10-27
 */
public class Závod_K implements IZávod
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    /** Plátno, na které se bude instance kreslit. */
    private static final SprávcePlátna SP = SprávcePlátna.getInstance();


    
//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================
//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================
//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================
    
    /** Mapa přiřazující jednotlivým závodníkům informace, 
     * které si o nich instance závodu pamatuje. */
    Map<IZávodník, Info> závodníci = new HashMap<IZávodník, Info>();

    /** Počet kol, na která se závod jede. */
    private final int početKol;

    /** Instance vytvářející okruhu, na nichž se závodí. */
    private final TovárnaNaKola továrna;
    
    /** Počet sloupců a řádků, které daný okruh zabrere. */
    private final int sloupcůOkruhu, řádkůOkruhu;

    /** Maximální přípustný maxZávodníků závodníků. */
    private final int maxZávodníků;

    /** Aktuálně nastavený modul plátna. */
    private final int modul;



//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    /** Systémový čas v okamžiku startu závodu. */
    private long čas0;

    /** Počet doposud registrovaných závodníků a tím i vytvořených okruhů. */
    private int poslední = 0;



//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Vytvoří instanci schopnou pořádat závody na okruzích
     * vytvářených instancemi třídy {@link TovárnaNaKola}.
     * 
     * @param tnk          Továrna, která bude vytvářet zádní okruhy
     * @param maxZávodníků Maximální maxZávodníků současně závodícíh závodníků
     * @param modul        Požadovaná délka strany jednoho pole plátna
     */
    public Závod_K(TovárnaNaKola tnk, int maxZávodníků, int modul)
    {
        this(tnk, maxZávodníků, modul, 1);
    }


    /***************************************************************************
     * Vytvoří instanci schopnou pořádat závody na okruzích
     * vrácených metodou {@link Okruh.elko(Pozice,Barva)}.
     *
     * @param tnk          Továrna, která bude vytvářet zádní okruhy
     * @param maxZávodníků Maximální maxZávodníků současně závodícíh závodníků
     * @param modul        Požadovaná délka strany jednoho pole plátna
     * @param početKol      Počet kol, na která se závod jede
     */
    public Závod_K(TovárnaNaKola tnk, int maxZávodníků, int modul, int početKol)
    {
        this.továrna      = tnk;
        this.sloupcůOkruhu= tnk.getSloupců();
        this.řádkůOkruhu  = tnk.getŘádků();
        this.maxZávodníků = maxZávodníků;
        this.modul        = modul;
        this.početKol     = početKol;
        
        SP.setKrokRozměr(modul, maxZávodníků*(řádkůOkruhu+1) - 1, řádkůOkruhu);
    }



//== ABSTRAKTNÍ METODY =========================================================
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================
//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Otevře dialogové okno, v němž závodníkům oznámí, že je odstartováno,
     * a poté, co je zavřou, změří systémový čas.
     */
    public void start()
    {
        IO.zpráva("Po potvrzení tohoto okna se začne měřit čas");
        Collection<Info> infa = závodníci.values();
        for (Info info : infa) {
            info.řadič.start();
        }
        čas0 = System.currentTimeMillis();
    }


    /***************************************************************************
     * Ukončí závod, čím zruší všechny registrujSePro a připraví se na nové.
     */
    public void stop()
    {
        závodníci.clear();
    }


    /***************************************************************************
     * Zaregistruje zadaného závodníka, umístí jej na start závodu,
     * zjistí si od uživatele potřebné klávesy
     * a přihlásí závodníka jako posluchače klávesnice.
     *
     * @param závodník Závodník registrující se pro daný závod
     */
    @Override
    public void registrace(IZávodník závodník)
    {
        if (poslední >= maxZávodníků) {
            IO.zpráva("Je možno zaregistrovat pouze: " + maxZávodníků +
                      " závodníků");
            return;
        }
        if (závodníci.containsKey(závodník)) {
            IO.zpráva("Závodníka nelze zaregistrovat dvakrát: " + závodník);
            return;
        }
        Okruh     okruh  = připravDalšíOkruh(); //Opraví i atribut poslední
        PoleCesty start  = okruh.getPočátek();
        Pozice    pozice = start.getPozice();
        Řadič     řadič  = Řadič.vytvořPro(závodník);
        PoleCesty cíl    = start.getDalší();

        Info info = new Info(okruh, start, cíl, řadič, početKol);
        závodníci.put(závodník, info);

        závodník.setPozice(pozice);
        závodník.setModul(modul);
        závodník.setSměr(start.getSměr());
    }


    /***************************************************************************
     * Prověří, že závodník dojel do správné průběžné cílové pozice.
     * Pokud dojel, připraví další průběžnou pozici, pokud nedojel, čeká dál.
     *
     * @param závodník Závodník hlásící změnu své pozice
     */
    @Override
    public void průjezdníKontrola(IZávodník závodník)
    {
        Info info = závodníci.get(závodník);
        if (info == null) {
            return;                      //==========>
        }
        Pozice pz = závodník.getPozice();
        Pozice pc = info.cíl.getPozice();
        if (pz.equals(pc)) {
            if (info.cíl == info.start) {
                info.zbýváKol--;
                if (info.zbýváKol == 0) {
                    konecZávodu(závodník, info);
                    return;                  //==========>
                }
            }
            info.cíl = info.cíl.getDalší();
        }
    }


    /***************************************************************************
     * Ukončí závod pro zadaného závodníka.
     *
     * @param závodník Závodník ukončivší závod
     */
    private void konecZávodu(IZávodník závodník, Info info)
    {
        long nyní = System.currentTimeMillis();
        info.řadič.konec();
        int čas = (int)(nyní - čas0 + 50) / 100;
        System.out.println("Závodík " + závodník.getNázev() +
               " absolvoval okruh v čase " + čas/10 + "," + čas%10 + " sekund");
        závodníci.remove(závodník);
        if (závodníci.isEmpty()) {
            System.out.println("Konec závodu");
//            IO.zpráva("Konec závodu" +
//                  "\n\nvýsledky najdete ve standardním výstupu");
        }
    }


    /***************************************************************************
     * Vrátí řetězec sloužící jako textový "podpis" instance
     * používaný především při ladění.
     */
    @Override
    public String toString()
    {
        return "Závod_L(ELKO, počet=" + maxZávodníků + ", modul=" + modul + ")";
    }



//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Připraví další okruh a vrátí odkaz na něj.
     *
     * @return Odkaz na připravený okruh
     */
    private Okruh připravDalšíOkruh()
    {
        int x = poslední * (sloupcůOkruhu+1) * modul;
        Pozice pozice = new Pozice(x, 0);
        Okruh okruh = továrna.vytvořOkruh(pozice, Barva.getBarva(poslední));
        poslední = poslední + 1;
        return okruh;
    }



//== INTERNÍ DATOVÉ TYPY =======================================================
    
    /***************************************************************************
     * Interní přepravka obsahující základní informace o závodníkovi
     * a průběžném stavu jeho závodu.
     */
    private static class Info
    {
        /** Okruh, na němž je závoděno. */
        private final Okruh okruh;

        /** Pole, z nějž závod odstartuje. */
        private final PoleCesty start;

        /** Příští pole, na které má závodník dojet
         *  a na němž se bude kontrolovat, zda je skutečně projel.  */
        private PoleCesty cíl;

        /** Řadič, zprostředkovávající ovládání závodníka z klávesnice. */
        private final Řadič řadič;

        /** Kontrolovaný závodník. */
        private int zbýváKol;
        

        /***********************************************************************
         * Definuje novou přepravku a inicializuje její atributy.
         */
        Info(Okruh okruh, PoleCesty start, PoleCesty cíl, Řadič řadič, int kol)
        {
            this.okruh    = okruh;
            this.start    = start;
            this.cíl      = cíl;
            this.řadič    = řadič;
            this.zbýváKol = kol;
        }               
    }



//== TESTY A METODA MAIN =======================================================

    /***************************************************************************
     * Testovací metoda.
     */
    public static void test()
    {
        TovárnaNaKola tnk = new TovárnaNaKola(2, 2, Směr8.JIH);
        Závod_K závod = new Závod_K(tnk, 1, 100, 3);

        Šipka  šipka = new Šipka(0, 0, Barva.BÍLÁ);
        Vozidlo_B v1 = new Vozidlo_B(šipka);
        v1.setNázev("Jednička");
        v1.registrujSePro(závod);

//        Auto  auto = new Auto(0, 0, Barva.BÍLÁ);
//        Vozidlo_B v2 = new Vozidlo_B(auto);
//        v2.setNázev("Dvojka");
//        v2.registrujSePro(závod);

        závod.start();
    }
//    /** @param args Parametry příkazového řádku - nepoužívané. */
//    public static void main( String[] args )  {  test();  }
}

